Bug 6456

Bug Description:

This is a race.
This bug is caused by multi-threaded accessing to the non-thread-safe class java.util.regex.Matcher at groovy.servlet.AbstractHttpServlet#applyResourceNameMatcher at:

matcher.reset(uri); (groovy.servlet.AbstractHttpServlet 280)

Two exceptions may be thrown out by running the test:
1) java.lang.IndexOutOfBoundsException:

Exception in thread "Thread-6" java.lang.IndexOutOfBoundsException: start 14, end 13, s.length() 31
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:470)
at java.lang.StringBuffer.append(StringBuffer.java:313)
at java.util.regex.Matcher.appendReplacement(Matcher.java:756)
at java.util.regex.Matcher.replaceAll(Matcher.java:823)
at groovy.servlet.AbstractHttpServlet.applyResourceNameMatcher(AbstractHttpServlet.java:283)
at groovy.servlet.AbstractHttpServlet.getScriptUri(AbstractHttpServlet.java:251)
at groovy.servlet.Groovy6456$TestThread.run(Groovy6456.java:77)

2)java.lang.IllegalStateException

Exception in thread "Thread-1" java.lang.IllegalStateException: No match available
at java.util.regex.Matcher.appendReplacement(Matcher.java:692)
at java.util.regex.Matcher.replaceAll(Matcher.java:813)
at groovy.servlet.AbstractHttpServlet.applyResourceNameMatcher(AbstractHttpServlet.java:283)
at groovy.servlet.AbstractHttpServlet.getScriptUri(AbstractHttpServlet.java:251)
at groovy.servlet.Groovy6456$TestThread.run(Groovy6456.java:90)

More details about this bug are at GROOVY-6456 JIRA page.

Interleaving Description:

Numbers in following code snippets are program global execution sequence. And number in brackets present an alternative interleaving to get another exception.

groovy.servlet.AbstractHttpServlet:

t1 t2 private String applyResourceNameMatcher(final String aUri) {
...
1 3(4) matcher.reset(uri);
String replaced;
if (resourceNameReplaceAll) {
2 replaced = matcher.replaceAll(resourceNameReplacement);
}
...
}

java.util.regex.Matcher:

public Matcher appendReplacement(StringBuffer sb, String replacement) {
// If no match, return error
4(3) if (first < 0)
throw new IllegalStateException("No match available");
...
(5) sb.append(getSubSequence(lastAppendPosition, first));
...
}

a) thread 1 resets matcher at 1.
b) thread 1 calls replaceall at 2 and comes to 4.
c) before thread 1 executes 4, context switched, thread 2 comes to 3.
d) thread 2 resets matcher at 3 and first at 4 is a negative number now.
e) thread 1 executes 4, and throws IllegalStateException.

Or alternatively,
a) thread 1 resets matcher at 1.
b) thread 1 calls replaceall at 2.
c) thread 1 executes (3),and comes to (5)
d) before thread 1 executes (5), context switched, thread 2 executes (4) first.
e) now first is set to a negative value in step d), thread 1 executes (5), an IndexOutOfBoundsException is thrown.

How To Reproduce:

This bug is reproduced under groovy 1.7.9 and JDK 1.6.0_33.
Execute the following scripts to run the test to reproduce the bug (assume the location of the groovy test project is groovy_test_home).

Linux:
${groovy_test_home}/scripts/run6456.sh [--threadnum arg]
Windows:
%groovy_test_home%\scripts\run6456.bat [-threadnum arg]

The default argument values will be taken if not specified. For example,
${groovy_test_home}/scripts/run6456.sh
is the same as
${groovy_test_home}/scripts/run6456.sh -threadnum 50

Option Function Default Value Valid Values
--threadnum,-tn thread number 50 integer